﻿using System;
using System.Data.SqlTypes;
using System.Globalization;
using GeoAPI.Coordinates;
using GeoAPI.Geometries;
using Microsoft.SqlServer.Server;

namespace MsSqlSpatial.UserDefinedTypes
{
    [Serializable]
    [SqlUserDefinedType(
        Format.Native,
        IsByteOrdered = true,
        IsFixedLength = true,
        Name = "MsSqlSpatialEnvelope")]
    public struct MsSqlSpatialEnvelope : INullable //, IBinarySerialize
    {
        private SqlDouble _bottom;
        private SqlDouble _left, _right;
        private SqlDouble _top;

        internal MsSqlSpatialEnvelope(IExtents2D ex)
        {
            if (!ex.IsEmpty)
            {
                _left = new SqlDouble(ex.Min[Ordinates.X]);
                _bottom = new SqlDouble(ex.Min[Ordinates.Y]);
                _right = new SqlDouble(ex.Max[Ordinates.X]);
                _top = new SqlDouble(ex.Max[Ordinates.Y]);
            }
            else
            {
                _left = new SqlDouble();
                _bottom = new SqlDouble();
                _right = new SqlDouble();
                _top = new SqlDouble();
            }
        }

        internal MsSqlSpatialEnvelope(IGeometry geom)
        {
            if (geom != null)
            {
                var ex = (IExtents2D) geom.Extents;
                _left = new SqlDouble(ex.Min[Ordinates.X]);
                _bottom = new SqlDouble(ex.Min[Ordinates.Y]);
                _right = new SqlDouble(ex.Max[Ordinates.X]);
                _top = new SqlDouble(ex.Max[Ordinates.Y]);
            }
            else
            {
                _left = new SqlDouble();
                _bottom = new SqlDouble();
                _right = new SqlDouble();
                _top = new SqlDouble();
            }
        }

        internal MsSqlSpatialEnvelope(SqlDouble left, SqlDouble bottom, SqlDouble right, SqlDouble top)
        {
            _left = left;
            _bottom = bottom;
            _right = right;
            _top = top;
        }

        public static MsSqlSpatialEnvelope Null
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get { return new MsSqlSpatialEnvelope(); }
        }

        public SqlDouble MinY
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get { return _bottom; }
        }

        public SqlDouble MinX
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get { return _left; }
        }

        public SqlDouble MaxX
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get { return _right; }
        }

        public SqlDouble MaxY
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get { return _top; }
        }

        #region INullable Members

        public bool IsNull
        {
            get
            {
                if (!_bottom.IsNull
                    && !_top.IsNull
                    && !_right.IsNull
                    && !_left.IsNull)
                    return false;
                return true;
            }
        }

        #endregion

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Intersects(MsSqlSpatialEnvelope other)
        {
            if (IsNull || other.IsNull)
                return false;

            if (_right < other._left
                || _left > other._right
                || _top < other._bottom
                || _bottom > other._top)
                return false;

            return true;
        }


        [SqlMethod(IsDeterministic = true, IsPrecise = true)]
        public static MsSqlSpatialEnvelope CreateEnvelope(SqlDouble left, SqlDouble bottom, SqlDouble right,
                                                          SqlDouble top)
        {
            return new MsSqlSpatialEnvelope(left, bottom, right, top);
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None)]
        public static MsSqlSpatialEnvelope Parse(SqlString s)
        {
            var env = new MsSqlSpatialEnvelope();

            if (s.IsNull)
                return env;
            string str = s.Value;
            string[] arr = str.Split(new[] {':', ','});
            if (arr.Length != 4)
                return env;
            double x1, y1, x2, y2;

            if (
                double.TryParse(arr[0], out x1)
                && double.TryParse(arr[1], out y1)
                && double.TryParse(arr[2], out x2)
                && double.TryParse(arr[3], out y2))
            {
                env._left = x1;
                env._bottom = y1;
                env._right = x2;
                env._top = y2;
            }
            return env;
        }

        public override string ToString()
        {
            return string.Format(NumberFormatInfo.InvariantInfo, "{0},{1}:{2},{3}", _left, _bottom, _right, _top);
        }


        //#region IBinarySerialize Members

        //public void Read(System.IO.BinaryReader r)
        //{
        //    if (r.ReadBoolean())
        //    {
        //        _left = r.ReadDouble();
        //        _bottom = r.ReadDouble();
        //        _right = r.ReadDouble();
        //        _top = r.ReadDouble();
        //        _top = r.ReadDouble();
        //    }
        //}

        //public void Write(System.IO.BinaryWriter w)
        //{
        //    w.Write(!IsNull);
        //    w.Write(_left.Value);
        //    w.Write(_bottom.Value);
        //    w.Write(_right.Value);
        //    w.Write(_top.Value);
        //}

        //#endregion
    }
}